// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Xamarin.Tests;
using Xamarin.Utils;
using Xunit;

namespace Microsoft.Macios.Generator.Tests;

// Unit test that ensures that all the generator attributes are correctly added in the compilation initialization
public class BindingSourceGeneratorGeneratorTests : BaseGeneratorTestClass {

	const string usingImportInput = @"
using System;
using Foundation;
using ObjCBindings;
namespace TestNamespace;

[BindingType<Class> (Name = ""AVAudioPCMBuffer"", Flags=Class.DisableDefaultCtor)]
public partial class AVAudioPcmBuffer : AVAudioBuffer {
}
";


	const string usingImportOutput = @"// <auto-generated />

#nullable enable

using Foundation;
using ObjCBindings;
using ObjCRuntime;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace TestNamespace;

[Register (""AVAudioPCMBuffer"", true)]
public partial class AVAudioPcmBuffer
{
	[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
	static readonly global::ObjCRuntime.NativeHandle class_ptr = global::ObjCRuntime.Class.GetHandle (""AVAudioPCMBuffer"");

	/// <summary>The Objective-C class handle for this class.</summary>
	/// <value>The pointer to the Objective-C class.</value>
	/// <remarks>
	///     Each managed class mirrors an unmanaged Objective-C class.
	///     This value contains the pointer to the Objective-C class.
	///     It is similar to calling the managed <see cref=\""ObjCRuntime.Class.GetHandle(string)\"" /> or the native <see href=\""https://developer.apple.com/documentation/objectivec/1418952-objc_getclass\"">objc_getClass</see> method with the type name.
	/// </remarks>
	public override global::ObjCRuntime.NativeHandle ClassHandle => class_ptr;

	/// <summary>Constructor to call on derived classes to skip initialization and merely allocate the object.</summary>
	/// <param name=""t"">Unused sentinel value, pass NSObjectFlag.Empty.</param>
	/// <remarks>
	///     <para>
	///         This constructor should be called by derived classes when they completely construct the object in managed code and merely want the runtime to allocate and initialize the <see cref=""Foundation.NSObject"" />.
	///         This is required to implement the two-step initialization process that Objective-C uses, the first step is to perform the object allocation, the second step is to initialize the object.
	///         When developers invoke this constructor, they take advantage of a direct path that goes all the way up to <see cref=""Foundation.NSObject"" /> to merely allocate the object's memory and bind the Objective-C and C# objects together.
	///         The actual initialization of the object is up to the developer.
	///     </para>
	///     <para>
	///         This constructor is typically used by the binding generator to allocate the object, but prevent the actual initialization to take place.
	///         Once the allocation has taken place, the constructor has to initialize the object.
	///         With constructors generated by the binding generator this means that it manually invokes one of the ""init"" methods to initialize the object.
	///     </para>
	///     <para>It is the developer's responsibility to completely initialize the object if they chain up using this constructor chain.</para>
	///     <para>
	///         In general, if the developer's constructor invokes the corresponding base implementation, then it should also call an Objective-C init method.
	///         If this is not the case, developers should instead chain to the proper constructor in their class.
	///     </para>
	///     <para>
	///         The argument value is ignored and merely ensures that the only code that is executed is the construction phase is the basic <see cref=""Foundation.NSObject"" /> allocation and runtime type registration.
	///         Typically the chaining would look like this:
	///     </para>
	///     <example>
	///             <code lang=""csharp lang-csharp""><![CDATA[
	/// //
	/// // The NSObjectFlag constructor merely allocates the object and registers the C# class with the Objective-C runtime if necessary.
	/// // No actual initXxx method is invoked, that is done later in the constructor
	/// //
	/// // This is taken from the iOS SDK's source code for the UIView class:
	/// //
	/// [Export (""initWithFrame:"")]
	/// public UIView (System.Drawing.RectangleF frame) : base (NSObjectFlag.Empty)
	/// {
	///     // Invoke the init method now.
	///     var initWithFrame = new Selector (""initWithFrame:"").Handle;
	///     if (IsDirectBinding) {
	///         Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSend_CGRect (this.Handle, initWithFrame, frame);
	///     } else {
	///         Handle = ObjCRuntime.Messaging.IntPtr_objc_msgSendSuper_CGRect (this.SuperHandle, initWithFrame, frame);
	///     }
	/// }
	/// ]]></code>
	///     </example>
	/// </remarks>
	[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
	[EditorBrowsable (EditorBrowsableState.Advanced)]
	protected AVAudioPcmBuffer (global::Foundation.NSObjectFlag t) : base (t) {}

	/// <summary>A constructor used when creating managed representations of unmanaged objects. Called by the runtime.</summary>
	/// <param name=""handle"">Pointer (handle) to the unmanaged object.</param>
	/// <remarks>
	///     <para>
	///         This constructor is invoked by the runtime infrastructure (<see cref=""ObjCRuntime.Runtime.GetNSObject(System.IntPtr)"" />) to create a new managed representation for a pointer to an unmanaged Objective-C object.
	///         Developers should not invoke this method directly, instead they should call <see cref=""ObjCRuntime.Runtime.GetNSObject(System.IntPtr)"" /> as it will prevent two instances of a managed object pointing to the same native object.
	///     </para>
	/// </remarks>
	[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
	[EditorBrowsable (EditorBrowsableState.Advanced)]
	protected internal AVAudioPcmBuffer (global::ObjCRuntime.NativeHandle handle) : base (handle) {}

}
";

	[Theory]
	[AllSupportedPlatforms (usingImportInput, usingImportOutput)]
	public void CorrectUsingImports (ApplePlatform platform, string input, string expectedOutput)
	{
		// We need to create a compilation with the required source code.
		var (compilation, _) = CreateCompilation (platform, sources: input);

		// Run generators and retrieve all results.
		var driver = CSharpGeneratorDriver.Create (new BindingSourceGeneratorGenerator ());
		var runResult = driver.RunGenerators (compilation).GetRunResult ();
		Assert.Empty (runResult.Diagnostics);

		// ensure that we do have all the needed attributes present
		var generatedFile = runResult.GeneratedTrees.SingleOrDefault (t => t.FilePath.EndsWith ("AVAudioPcmBuffer.g.cs"));
		Assert.NotNull (generatedFile);
		Assert.Equal (expectedOutput, generatedFile.GetText ().ToString ());
	}
}
